package vip.liux.backend.infrastructure.excel.handler;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.read.metadata.holder.ReadSheetHolder;
import com.google.common.collect.Sets;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import vip.liux.backend.infrastructure.excel.ErrorMessage;
import vip.liux.backend.infrastructure.excel.hook.ImportExcelHook;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Excel 导入基础监听器，支持数据校验、钩子处理、错误收集等功能。
 *
 * @param <T> 导入数据类型
 */
@Slf4j
public abstract class BaseAnalysisEventListener<T> extends AnalysisEventListener<T> {

    /**
     * 解析成功的数据列表
     */
    @Getter
    private final List<T> list = new ArrayList<>();
    /**
     * 错误信息列表
     */
    @Getter
    private final List<ErrorMessage> errorMessageList = new ArrayList<>();

    /**
     * JSR-303 校验器
     */
    @Setter
    private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    /**
     * 导入钩子集合
     */
    @Getter
    private List<ImportExcelHook> hooks;

    /**
     * 设置钩子类（通过反射实例化）
     *
     * @param hooks 钩子类数组
     */
    public void setHooks(List<Class<? extends ImportExcelHook>> hooks) {
        if (CollectionUtils.isEmpty(hooks)) {
            return;
        }
        this.hooks = hooks.stream().map(h -> {
            try {
                return ConstructorUtils.invokeConstructor(h);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException |
                     InstantiationException e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
    }

    /**
     * 每读取一行数据时回调
     *
     * @param o               当前行数据
     * @param analysisContext 上下文
     */
    @Override
    public void invoke(T o, AnalysisContext analysisContext) {
        // 钩子处理
        if (CollectionUtils.isNotEmpty(this.getHooks())) {
            this.getHooks().forEach(importExcelHook -> importExcelHook.invokeHook(o, analysisContext));
        }
        Integer rowIndex = ((ReadSheetHolder) analysisContext.currentReadHolder()).getRowIndex();
        // 校验数据
        if (validateObject(o, rowIndex)) {
            list.add(o);
        }
    }

    /**
     * 所有数据解析完成后回调
     *
     * @param analysisContext 上下文
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        if (CollectionUtils.isNotEmpty(this.getHooks())) {
            this.getHooks()
                    .forEach(importExcelHook -> importExcelHook.doAfterAllAnalysedHook(getList(), getErrorMessageList()));
        }
        log.debug("Excel read analysed");
    }

    /**
     * 解析异常处理
     *
     * @param exception 异常
     * @param context   上下文
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.debug("解析失败:{}", exception.getMessage());

        if (exception instanceof ExcelDataConvertException ex) {
            ReadRowHolder readRowHolder = context.readRowHolder();
            Integer rowIndex = readRowHolder.getRowIndex();
            errorMessageList.add(new ErrorMessage(rowIndex, Sets.newHashSet(ex.getMessage())));
        }
    }

    /**
     * 校验对象，收集校验失败信息
     *
     * @param o        校验对象
     * @param rowIndex 行号
     * @return 校验通过返回true，否则false
     */
    private boolean validateObject(Object o, Integer rowIndex) {
        Set<ConstraintViolation<Object>> violations = validator.validate(o);
        if (CollectionUtils.isEmpty(violations)) {
            return Boolean.TRUE;
        }
        Set<String> messageSet = violations.stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.toSet());
        errorMessageList.add(new ErrorMessage(rowIndex, messageSet));
        return Boolean.FALSE;
    }

}